/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2006 Tim Angus

This file is part of Tremulous.

Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "server.h"

#ifdef USE_VOIP
cvar_t *sv_voip;
#endif

serverStatic_t  svs;        // persistant server info
server_t    sv;         // local server
vm_t      *gvm = NULL;        // game virtual machine

cvar_t  *sv_fps;        // time rate for running non-clients
cvar_t  *sv_timeout;      // seconds without any message
cvar_t  *sv_zombietime;     // seconds to sink messages after disconnect
cvar_t  *sv_rconPassword;   // password for remote server commands
cvar_t  *sv_rconLog;      // log file for remote server commands
cvar_t  *sv_privatePassword;  // password for the privateClient slots
cvar_t  *sv_allowDownload;
cvar_t  *sv_maxclients;

cvar_t  *sv_privateClients;   // number of clients reserved for password
cvar_t  *sv_hostname;
cvar_t  *sv_master[MAX_MASTER_SERVERS];   // master server ip address
cvar_t  *sv_reconnectlimit;   // minimum seconds between connect messages
cvar_t  *sv_showloss;     // report when usercmds are lost
cvar_t  *sv_padPackets;     // add nop bytes to messages
cvar_t  *sv_killserver;     // menu system can set to 1 to shut server down
cvar_t  *sv_mapname;
cvar_t  *sv_mapChecksum;
cvar_t  *sv_serverid;
cvar_t  *sv_minRate;
cvar_t  *sv_maxRate;
cvar_t  *sv_minPing;
cvar_t  *sv_maxPing;
cvar_t  *sv_pure;
cvar_t  *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
cvar_t  *sv_dequeuePeriod;
cvar_t  *sv_semipurePrefix;
cvar_t  *sv_semipureOfferDownloads;

/*
=============================================================================

EVENT MESSAGES

=============================================================================
*/

/*
===============
SV_ExpandNewlines

Converts newlines to "\n" so a line prints nicer
===============
*/
char  *SV_ExpandNewlines( char *in ) {
  static  char  string[1024];
  int   l;

  l = 0;
  while ( *in && l < sizeof(string) - 3 ) {
    if ( *in == '\n' ) {
      string[l++] = '\\';
      string[l++] = 'n';
    } else {
      string[l++] = *in;
    }
    in++;
  }
  string[l] = 0;

  return string;
}

/*
======================
SV_ReplacePendingServerCommands

  This is ugly
======================
*/
int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
  int i, index, csnum1, csnum2;

  for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
    index = i & ( MAX_RELIABLE_COMMANDS - 1 );
    //
    if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
      sscanf(cmd, "cs %i", &csnum1);
      sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
      if ( csnum1 == csnum2 ) {
        Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
        return qtrue;
      }
    }
  }
  return qfalse;
}

/*
======================
SV_AddServerCommand

The given command will be transmitted to the client, and is guaranteed to
not have future snapshot_t executed before it is executed
======================
*/
void SV_AddServerCommand( client_t *client, const char *cmd ) {
  int   index, i;

  // this is very ugly but it's also a waste to for instance send multiple config string updates
  // for the same config string index in one snapshot
//  if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
//    return;
//  }

  // do not send commands until the gamestate has been sent
  if( client->state < CS_PRIMED )
    return;

  client->reliableSequence++;
  // if we would be losing an old command that hasn't been acknowledged,
  // we must drop the connection
  // we check == instead of >= so a broadcast print added by SV_DropClient()
  // doesn't cause a recursive drop client
  if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
    Com_Printf( "===== pending server commands =====\n" );
    for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
      Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
    }
    Com_Printf( "cmd %5d: %s\n", i, cmd );
    SV_DropClient( client, "Server command overflow" );
    return;
  }
  index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
  Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
}

/*
=================
SV_SendServerCommand

Sends a reliable command string to be interpreted by 
the client game module: "cp", "print", "chat", etc
A NULL client will broadcast to all clients
=================
*/
void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
  va_list   argptr;
  byte    message[MAX_MSGLEN];
  client_t  *client;
  int     j;
  
  va_start (argptr,fmt);
  Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr);
  va_end (argptr);

  // Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt
  // The actual cause of the bug is probably further downstream
  // and should maybe be addressed later, but this certainly
  // fixes the problem for now
  if ( strlen ((char *)message) > 1022 ) {
    return;
  }

  if ( cl != NULL ) {
    SV_AddServerCommand( cl, (char *)message );
    return;
  }

  // hack to echo broadcast prints to console
  if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
    Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
  }

  // send the data to all relevent clients
  for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
    //if(client->state == CS_ACTIVE)
      SV_AddServerCommand( client, (char *)message );
  }
}

/*
==============================================================================

MASTER SERVER FUNCTIONS

==============================================================================
*/

/*
================
SV_MasterHeartbeat

Send a message to the masters every few minutes to
let it know we are alive, and log information.
We will also have a heartbeat sent when a server
changes from empty to non-empty, and full to non-full,
but not on every player enter or exit.
================
*/
#define HEARTBEAT_MSEC  300*1000
#define HEARTBEAT_GAME  "Tremulous"
void SV_MasterHeartbeat( void ) {
  static netadr_t adr[MAX_MASTER_SERVERS];
  int     i;

  // "dedicated 1" is for lan play, "dedicated 2" is for inet public play
  if ( !com_dedicated || com_dedicated->integer != 2 ) {
    return;   // only dedicated servers send heartbeats
  }

  // if not time yet, don't send anything
  if ( svs.time < svs.nextHeartbeatTime ) {
    return;
  }
  svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;


  // send to group masters
  for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
    if ( !sv_master[i]->string[0] ) {
      continue;
    }

    // see if we haven't already resolved the name
    // resolving usually causes hitches on win95, so only
    // do it when needed
    if ( sv_master[i]->modified ) {
      sv_master[i]->modified = qfalse;
  
      Com_Printf( "Resolving %s\n", sv_master[i]->string );
      if ( !NET_StringToAdr( sv_master[i]->string, &adr[i], NA_UNSPEC ) ) {
        Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string );
        continue;
      }
      if ( !strchr( sv_master[i]->string, ':' ) ) {
        adr[i].port = BigShort( PORT_MASTER );
      }
      Com_Printf( "%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i]));
    }


    Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string );
    // this command should be changed if the server info / status format
    // ever incompatably changes
    NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME );
  }
}

/*
=================
SV_MasterShutdown

Informs all masters that this server is going down
=================
*/
void SV_MasterShutdown( void ) {
  // send a hearbeat right now
  svs.nextHeartbeatTime = -9999;
  SV_MasterHeartbeat();

  // send it again to minimize chance of drops
  svs.nextHeartbeatTime = -9999;
  SV_MasterHeartbeat();

  // when the master tries to poll the server, it won't respond, so
  // it will be removed from the list
}

/*
=================
SV_MasterGameStat
=================
*/
void SV_MasterGameStat( const char *data )
{
  static netadr_t adr;

  if( !com_dedicated || com_dedicated->integer != 2 )
    return;   // only dedicated servers send stats

  Com_Printf( "Resolving %s\n", MASTER_SERVER_NAME );
  if( !NET_StringToAdr( MASTER_SERVER_NAME, &adr, NA_IP ) )
  {
    Com_Printf( "Couldn't resolve address: %s\n", MASTER_SERVER_NAME );
    return;
  }
  else
  {
    if( !strstr( ":", MASTER_SERVER_NAME ) )
      adr.port = BigShort( PORT_MASTER );

    Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", MASTER_SERVER_NAME,
      adr.ip[0], adr.ip[1], adr.ip[2], adr.ip[3],
      BigShort( adr.port ) );
  }

  Com_Printf ("Sending gamestat to %s\n", MASTER_SERVER_NAME );
  NET_OutOfBandPrint( NS_SERVER, adr, "gamestat %s", data );
}

/*
==============================================================================

CONNECTIONLESS COMMANDS

==============================================================================
*/

/*
================
SVC_Status

Responds with all the info that qplug or qspy can see about the server
and all connected players.  Used for getting detailed information after
the simple info query.
================
*/
void SVC_Status( netadr_t from ) {
  char  player[1024];
  char  status[MAX_MSGLEN];
  int   i;
  client_t  *cl;
  playerState_t *ps;
  int   statusLength;
  int   playerLength;
  char  infostring[MAX_INFO_STRING];

  strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );

  // echo back the parameter to status. so master servers can use it as a challenge
  // to prevent timed spoofed reply packets that add ghost servers
  Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );

  status[0] = 0;
  statusLength = 0;

  for (i=0 ; i < sv_maxclients->integer ; i++) {
    cl = &svs.clients[i];
    if ( cl->state >= CS_CONNECTED ) {
      ps = SV_GameClientNum( i );
      Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", 
        ps->persistant[PERS_SCORE], cl->ping, cl->name);
      playerLength = strlen(player);
      if (statusLength + playerLength >= sizeof(status) ) {
        break;    // can't hold any more
      }
      strcpy (status + statusLength, player);
      statusLength += playerLength;
    }
  }

  NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
}

/*
================
SVC_Info

Responds with a short info message that should be enough to determine
if a user is interested in a server to do a full status
================
*/
void SVC_Info( netadr_t from ) {
  int   i, count;
  char  *gamedir;
  char  infostring[MAX_INFO_STRING];

  /*
   * Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led
   * to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory.
   */

  // A maximum challenge length of 128 should be more than plenty.
  if(strlen(Cmd_Argv(1)) > 128)
    return;

  // don't count privateclients
  count = 0;
  for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) {
    if ( svs.clients[i].state >= CS_CONNECTED ) {
      count++;
    }
  }

  infostring[0] = 0;

  // echo back the parameter to status. so servers can use it as a challenge
  // to prevent timed spoofed reply packets that add ghost servers
  Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );

  Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) );
  Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
  Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
  Info_SetValueForKey( infostring, "clients", va("%i", count) );
  Info_SetValueForKey( infostring, "sv_maxclients", 
    va("%i", sv_maxclients->integer - sv_privateClients->integer ) );
  Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );

#ifdef USE_VOIP
  if (sv_voip->integer) {
    Info_SetValueForKey( infostring, "voip", va("%i", sv_voip->integer ) );
  }
#endif

  if( sv_minPing->integer ) {
    Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
  }
  if( sv_maxPing->integer ) {
    Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
  }
  gamedir = Cvar_VariableString( "fs_game" );
  if( *gamedir ) {
    Info_SetValueForKey( infostring, "game", gamedir );
  }

  NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
}

/*
================
SVC_FlushRedirect

================
*/
void SV_FlushRedirect( char *outputbuf ) {
  NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
}

/*
===============
SVC_RemoteCommand

An rcon packet arrived from the network.
Shift down the remaining args
Redirect all printfs
===============
*/
void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
  qboolean  valid;
  unsigned int time;
  char    remaining[1024];
  // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
  // (OOB messages are the bottleneck here)
#define SV_OUTPUTBUF_LENGTH (1024 - 16)
  char    sv_outputbuf[SV_OUTPUTBUF_LENGTH];
  static unsigned int lasttime = 0;
  char *cmd_aux;
  fileHandle_t rconLog = 0;

  // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
  time = Com_Milliseconds();
  if ( (unsigned)( time - lasttime ) < 500u ) {
    return;
  }
  lasttime = time;

  if(strlen(sv_rconLog->string)) {
    rconLog = FS_FOpenFileAppend(sv_rconLog->string);
    if (!rconLog) {
      Com_Printf("Warning: Unable to open sv_rconLog: \"%s\"", sv_rconLog->string);
      Cvar_Set ("sv_rconLog", "");
    }
  }
  
  const char *message = "";
  if ( !strlen( sv_rconPassword->string ) ||
    strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
    valid = qfalse;
    message = va("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2));
  } else {
    valid = qtrue;
    message = va("Rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2));
  }
  
  Com_Printf (message);
  if (rconLog) {
    qtime_t qt;
    Com_RealTime(&qt);
    char *timestamp = va( "%02i/%02i/%02i %02i:%02i:%02i  ", qt.tm_mday, qt.tm_mon, qt.tm_year-100, qt.tm_hour, qt.tm_min, qt.tm_sec );
    FS_Write(timestamp, strlen(timestamp), rconLog);
    FS_Write(message, strlen(message), rconLog);
    FS_FCloseFile(rconLog);
  }
  
  // start redirecting all print outputs to the packet
  svs.redirectAddress = from;
  Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);

  if ( !strlen( sv_rconPassword->string ) ) {
    Com_Printf ("No rconpassword set on the server.\n");
  } else if ( !valid ) {
    Com_Printf ("Bad rconpassword.\n");
  } else {
    remaining[0] = 0;
    
    // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
    // get the command directly, "rcon <pass> <command>" to avoid quoting issues
    // extract the command by walking
    // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing
    cmd_aux = Cmd_Cmd();
    cmd_aux+=4;
    while(cmd_aux[0]==' ')
      cmd_aux++;
    while(cmd_aux[0] && cmd_aux[0]!=' ') // password
      cmd_aux++;
    while(cmd_aux[0]==' ')
      cmd_aux++;
    
    Q_strcat( remaining, sizeof(remaining), cmd_aux);
    
    Cmd_ExecuteString (remaining);

  }

  Com_EndRedirect ();
}

/*
=================
SV_ConnectionlessPacket

A connectionless packet has four leading 0xff
characters to distinguish it from a game channel.
Clients that are in the game can still send
connectionless packets.
=================
*/
void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
  char  *s;
  char  *c;

  MSG_BeginReadingOOB( msg );
  MSG_ReadLong( msg );    // skip the -1 marker

  if (!Q_strncmp("connect", (char *) &msg->data[4], 7)) {
    Huff_Decompress(msg, 12);
  }

  s = MSG_ReadStringLine( msg );
  Cmd_TokenizeString( s );

  c = Cmd_Argv(0);
  Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);

  if (!Q_stricmp(c, "getstatus")) {
    SVC_Status( from  );
  } else if (!Q_stricmp(c, "getinfo")) {
    SVC_Info( from );
  } else if (!Q_stricmp(c, "getchallenge")) {
    SV_GetChallenge( from );
  } else if (!Q_stricmp(c, "connect")) {
    SV_DirectConnect( from );
  } else if (!Q_stricmp(c, "rcon")) {
    SVC_RemoteCommand( from, msg );
  } else if (!Q_stricmp(c, "disconnect")) {
    // if a client starts up a local server, we may see some spurious
    // server disconnect messages when their new server sees our final
    // sequenced messages to the old client
  } else {
    Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
    , NET_AdrToString (from), s);
  }
}

//============================================================================

/*
=================
SV_ReadPackets
=================
*/
void SV_PacketEvent( netadr_t from, msg_t *msg ) {
  int     i;
  client_t  *cl;
  int     qport;

  // check for connectionless packet (0xffffffff) first
  if ( msg->cursize >= 4 && *(int *)msg->data == -1) {
    SV_ConnectionlessPacket( from, msg );
    return;
  }

  // read the qport out of the message so we can fix up
  // stupid address translating routers
  MSG_BeginReadingOOB( msg );
  MSG_ReadLong( msg );        // sequence number
  qport = MSG_ReadShort( msg ) & 0xffff;

  // find which client the message is from
  for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
    if (cl->state == CS_FREE) {
      continue;
    }
    if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) {
      continue;
    }
    // it is possible to have multiple clients from a single IP
    // address, so they are differentiated by the qport variable
    if (cl->netchan.qport != qport) {
      continue;
    }

    // the IP port can't be used to differentiate them, because
    // some address translating routers periodically change UDP
    // port assignments
    if (cl->netchan.remoteAddress.port != from.port) {
      Com_Printf( "SV_PacketEvent: fixing up a translated port\n" );
      cl->netchan.remoteAddress.port = from.port;
    }

    // make sure it is a valid, in sequence packet
    if (SV_Netchan_Process(cl, msg)) {
      // zombie clients still need to do the Netchan_Process
      // to make sure they don't need to retransmit the final
      // reliable message, but they don't do any other processing
      if (cl->state != CS_ZOMBIE) {
        cl->lastPacketTime = svs.time;  // don't timeout
        SV_ExecuteClientMessage( cl, msg );
      }
    }
    return;
  }
  
  // if we received a sequenced packet from an address we don't recognize,
  // send an out of band disconnect packet to it
  NET_OutOfBandPrint( NS_SERVER, from, "disconnect" );
}


/*
===================
SV_CalcPings

Updates the cl->ping variables
===================
*/
void SV_CalcPings( void ) {
  int     i, j;
  client_t  *cl;
  int     total, count;
  int     delta;
  playerState_t *ps;

  for (i=0 ; i < sv_maxclients->integer ; i++) {
    cl = &svs.clients[i];
    if ( cl->state != CS_ACTIVE ) {
      cl->ping = 999;
      continue;
    }
    if ( !cl->gentity ) {
      cl->ping = 999;
      continue;
    }

    total = 0;
    count = 0;
    for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
      if ( cl->frames[j].messageAcked <= 0 ) {
        continue;
      }
      delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
      count++;
      total += delta;
    }
    if (!count) {
      cl->ping = 999;
    } else {
      cl->ping = total/count;
      if ( cl->ping > 999 ) {
        cl->ping = 999;
      }
    }

    // let the game dll know about the ping
    ps = SV_GameClientNum( i );
    ps->ping = cl->ping;
  }
}

/*
==================
SV_CheckTimeouts

If a packet has not been received from a client for timeout->integer 
seconds, drop the conneciton.  Server time is used instead of
realtime to avoid dropping the local client while debugging.

When a client is normally dropped, the client_t goes into a zombie state
for a few seconds to make sure any final reliable message gets resent
if necessary
==================
*/
void SV_CheckTimeouts( void ) {
  int   i;
  client_t  *cl;
  int     droppoint;
  int     zombiepoint;

  droppoint = svs.time - 1000 * sv_timeout->integer;
  zombiepoint = svs.time - 1000 * sv_zombietime->integer;

  for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
    // message times may be wrong across a changelevel
    if (cl->lastPacketTime > svs.time) {
      cl->lastPacketTime = svs.time;
    }

    if (cl->state == CS_ZOMBIE
    && cl->lastPacketTime < zombiepoint) {
      // using the client id cause the cl->name is empty at this point
      Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i );
      cl->state = CS_FREE;  // can now be reused
      continue;
    }
    if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) {
      // wait several frames so a debugger session doesn't
      // cause a timeout
      if ( ++cl->timeoutCount > 5 ) {
        SV_DropClient (cl, "timed out"); 
        cl->state = CS_FREE;  // don't bother with zombie state
      }
    } else {
      cl->timeoutCount = 0;
    }
  }
}


/*
==================
SV_CheckPaused
==================
*/
qboolean SV_CheckPaused( void ) {
  int   count;
  client_t  *cl;
  int   i;

  if ( !cl_paused->integer ) {
    return qfalse;
  }

  // only pause if there is just a single client connected
  count = 0;
  for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
    if ( cl->state >= CS_CONNECTED ) {
      count++;
    }
  }

  if ( count > 1 ) {
    // don't pause
    if (sv_paused->integer)
      Cvar_Set("sv_paused", "0");
    return qfalse;
  }

  if (!sv_paused->integer)
    Cvar_Set("sv_paused", "1");
  return qtrue;
}

/*
==================
SV_Frame

Player movement occurs as a result of packet events, which
happen before SV_Frame is called
==================
*/
void SV_Frame( int msec ) {
  int   frameMsec;
  int   startTime;

  // the menu kills the server with this cvar
  if ( sv_killserver->integer ) {
    SV_Shutdown ("Server was killed");
    Cvar_Set( "sv_killserver", "0" );
    return;
  }

  if (!com_sv_running->integer)
  {
    // Running as a server, but no map loaded
#ifdef DEDICATED
    // Block until something interesting happens
    Sys_Sleep(-1);
#endif

    return;
  }

  // allow pause if only the local client is connected
  if ( SV_CheckPaused() ) {
    return;
  }

  // if it isn't time for the next frame, do nothing
  if ( sv_fps->integer < 1 ) {
    Cvar_Set( "sv_fps", "10" );
  }

  frameMsec = 1000 / sv_fps->integer * com_timescale->value;
  // don't let it scale below 1ms
  if(frameMsec < 1)
  {
    Cvar_Set("timescale", va("%f", sv_fps->integer / 1000.0f));
    frameMsec = 1;
  }

  sv.timeResidual += msec;

  if ( com_dedicated->integer && sv.timeResidual < frameMsec ) {
    // NET_Sleep will give the OS time slices until either get a packet
    // or time enough for a server frame has gone by
    NET_Sleep(frameMsec - sv.timeResidual);
    return;
  }

  // if time is about to hit the 32nd bit, kick all clients
  // and clear sv.time, rather
  // than checking for negative time wraparound everywhere.
  // 2giga-milliseconds = 23 days, so it won't be too often
  if ( svs.time > 0x70000000 ) {
    SV_Shutdown( "Restarting server due to time wrapping" );
    Cbuf_AddText( va( "map %s\n", Cvar_VariableString( "mapname" ) ) );
    return;
  }
  // this can happen considerably earlier when lots of clients play and the map doesn't change
  if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) {
    SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" );
    Cbuf_AddText( va( "map %s\n", Cvar_VariableString( "mapname" ) ) );
    return;
  }

  if( sv.restartTime && sv.time >= sv.restartTime ) {
    sv.restartTime = 0;
    Cbuf_AddText( "map_restart 0\n" );
    return;
  }

  // update infostrings if anything has been changed
  if ( cvar_modifiedFlags & CVAR_SERVERINFO ) {
    SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
    cvar_modifiedFlags &= ~CVAR_SERVERINFO;
  }
  if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) {
    SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
    cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
  }

  if ( com_speeds->integer ) {
    startTime = Sys_Milliseconds ();
  } else {
    startTime = 0;  // quite a compiler warning
  }

  // update ping based on the all received frames
  SV_CalcPings();

  // run the game simulation in chunks
  while ( sv.timeResidual >= frameMsec ) {
    sv.timeResidual -= frameMsec;
    svs.time += frameMsec;
    sv.time += frameMsec;

    // let everything in the world think and move
    VM_Call (gvm, GAME_RUN_FRAME, sv.time);
  }

  if ( com_speeds->integer ) {
    time_game = Sys_Milliseconds () - startTime;
  }

  // check timeouts
  SV_CheckTimeouts();

  // send messages back to the clients
  SV_SendClientMessages();

  // send a heartbeat to the master if needed
  SV_MasterHeartbeat();
}

//============================================================================

